package com.sky.controller.admin;

import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 菜品管理
 * @author 岳海
 * @version 1.0
 * 312 / 356
 */
@RestController("adminDishController")
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {

    @Autowired
    private DishService dishService;

    @Autowired
    private RedisTemplate redisTemplate;   //添加清理redis缓存的代码逻辑,保证和mysql数据库数据更新的一致性
    //新增菜品, 修改菜品, 批量删除菜品, 起售、停售菜品  都需要保证数据的一致性
    //因为下面的方法,都要单独的处理这个缓存数据,,所以可以单独抽取出来写到一个方法里面,直接调用,,代码就更加规范
        //主要是用的太多了,代码又是独立的,,在当前类下面创建一个方法cleanCache,只在当前类使用

    /**
     * 新增菜品
     * 对应的文件上传,,在通用接口CommonController中
     * @param dishDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}",dishDTO);
        dishService.saveWithFlavor(dishDTO);

        //清理缓存数据
            //精确清理,就是当前新增菜品的分类要清理,,也就是删除;
            // 反正添加的时候,redis中没有了,就会缓存到redis(前面代码已经实现了)
        String key = "dish_" + dishDTO.getCategoryId();
        cleanCache(key);    //这里是精确清理,所以直接传入上面我们构建的key就可以了,,,不用直接传"dish_*"

        return Result.success();
    }

    /**
     * 菜品分页查询
     * 虽说是分页查询,,其实主要是整体查询所有菜品+后面关联的套餐;   分页的逻辑是在serviceImpl实现的分页,mapper层的查询和分页没关系
     *      前端页面有三种查询方式,根据菜品名,根据套餐id,根据售卖状态;即在上面整体查询的基础上通过where添加条件限制
     * @param dishPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("菜品分页查询")
    //在前面学员工分类查询的时候,就定义了PageResult这个类,所有的分页查询结果,都统一封装成这个PageResult对象
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
        //这里形参前面没有加@RequestBody注解,因为是直接Query请求的,就是地址栏直接传个简单参数,不是json格式

        log.info("菜品分页查询:{}",dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);

    }

    /**
     * 菜品批量删除
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("菜品批量删除")
    public Result delete(@RequestParam List<Long> ids){
        //这里可以用字符串整体接收String ids,如1,2,3 然后再单独把这三个解析出来就可以了;但需要自己来处理字符串
        //可以直接利用springmvc框架提供的,通过@RequestParam能够帮助我们把获取的参数,通过逗号直接隔开,
            // 然后存放到List集合,此时springmvc框架就能动态的去解析这个字符串,
            // 并且把对应的id都提取出来,最终封装到集合ids中
        log.info("菜品批量删除:{}",ids);   //话说这里可以直接输出集合吗,,,不用遍历吗??
        dishService.deleteBatch(ids);

        //这个删除的缓存麻烦一点,,因为你批量删除涉及到多个菜品,那可能涉及到多个菜品的分类,,
            // 也就是说可能涉及到多个redis中的key,那清理缓存就不能直接delete传入一个key了
            //那就还得单独查询,都有哪些被影响到了,那说明还得频繁查数据库,,
        //优化思想:  既然这么麻烦,老师说,那直接把redis中的所有菜品分类的缓存都删掉

        //将所有的菜品缓存数据清理掉, 所有以"dish_" 开头的key
        cleanCache("dish_*");

        return Result.success();
    }

    /**
     * 根据id查询菜品--(不是单纯的只根据id查菜品,还要查询出对应关联的口味表中的数据,,所以比较复杂)
     * 主要是这里的回显信息比较复杂,还要回显对应口味表中的信息
     * 并不是简单的直接根据id查询菜品,getById(不包含口味数据,,)其实在上面delete方法中对应mapper已经实现过那种最基本的简单查询
     * 感觉下面的方法名叫getByIdWithFlavor比较好,感觉getById很容易迷惑,不过跟着老师视频代码,就没修改
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询菜品(包括口味)")
    public Result<DishVO> getById(@PathVariable Long id){

        log.info("根据id查询菜品:{}",id);
        DishVO dishVO = dishService.getByIdWithFlavor(id);
        return Result.success(dishVO);
    }

    /**
     * 修改菜品和口味信息
     * @param dishDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改菜品(包括口味)")
    public Result update(@RequestBody DishDTO dishDTO){
        //因为只是修改,不是查询,所以不用写泛型了
        //老师说这里的修改和上面新增的方法,请求的数据和格式差不多都是一样的,所以可以直接用DishDTO来接收
        log.info("修改菜品信息:{}",dishDTO);
        dishService.updateWithFlavor(dishDTO);
        //这里修改的不仅仅修改菜品信息,对应口味表中的信息也要修改;

        //修改的操作也是比较复杂的,如果你只修改菜品名称啥的,那就是只删除一个对应的分类缓存就行;
            //但是如果它修改的是分类,比如一个汤类,它修改成水煮鱼类,,那一个类中要减少,一个要增加,就比较复杂
        //老师又说,因为修改操作不是常规操作,很少会修改,,,所以可以单独来处理下逻辑===最终老师也没单独处理呀,还是全删除
            // (自己内心:我直接给你都删除,和上面批量删除一样,还单独给你处理逻辑,你想得美,主要是太复杂了,要单独查询)

        //将所有的菜品缓存数据清理掉, 所有以"dish_" 开头的key
        cleanCache("dish_*");

        return Result.success();
    }


    /**
     *  菜品起售/停售
     */
    @PostMapping("/status/{status}")
    @ApiOperation("菜品起售/停售")
    public Result<String> startOrStop(@PathVariable Integer status,Long id){

         dishService.startOrStop(status,id);

         //这里可以精确的只清理对应key的缓存数据,,,不过 老师又是直接删除所有缓存,那你还分析屁的逻辑,
            // 直接用通用解决方案得了,全都删除,反正新添加访问数据库,redis中没有,就会把缓存数据添加到redis数据库中
            //因为这里传的是菜品id,要获得分类id,还得把菜品的数据都查出来,然后获取分类id,再构建key,那就得不偿失了

        //将所有的菜品缓存数据清理掉, 所有以"dish_" 开头的key
       cleanCache("dish_*");

        return Result.success();
    }

    /**
     * 根据分类id查询菜品
     *  看前端的页面,,感觉前面"菜品管理"对应的功能模块中,"分页查询",感觉已经实现了通过分类id来查询菜品的功能呀
     *  只不过当时分页查询,还把对应的 category表中的category的name也查出来了;;
     *  所以完全可以调用"分页查询"mapper层对应的pageQuery方法,,具体的分页逻辑是在service层实现的,和mapper层无关
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<Dish>>  list(Long categoryId){
        //这里为啥用List集合返回,因为你分类id查询的菜品不是一个;和单独的一个菜品id查一个菜品不一样

        List<Dish> list = dishService.list(categoryId);
        return Result.success(list);
    }


    /**
     * 清理缓存数据
     * 1.这个是专门用来清理redis缓存数据的,为了保证redis和mysql数据库的一致性
     * 2. 这个方法下面设置成了private,所以只在当前类中有效,别的类不能直接调用
     * @param pattern
     */
    private void cleanCache(String pattern){

        //将所有的菜品缓存数据清理掉,所以传入的pattern要构建成固定的格式,
            // 比如"dish_*",后面的*是通配符,表示匹配所有以"dish_" 开头的key

        Set keys = redisTemplate.keys(pattern);  //把所有的key都查出来
        redisTemplate.delete(keys);        //这里再把对应的key都删除,这里支持集合;就不用遍历一个个删除了
        //为啥这里要先获取所有的keys,而不能直接把pattern传入delete方法中,直接删除
            //因为你pattern比如是"dish_*",是有通配符*的,delete这个方法无法识别出它有通配符,而上面获取keys就可以
    }

}
